package com.quotes.datamodell;
import java.util.*;
import java.util.Map.Entry;

import org.jfree.data.time.Day;

import com.quotes.dates.TimeRange;

public class TimeSeries implements Cloneable
{
	private TreeMap<Date, Double> content = new TreeMap<Date, Double>();

	public TimeSeries() {
	}
	
	public TimeSeries(Date[] dates, double[] values) {
		for (int i=0; i<dates.length; i++) {
			content.put(dates[i], values[i]);
		}
	}
	
	public TimeSeries(Date[] dates, List<Double> values) {
		for (int i=0; i<dates.length; i++) {
			content.put(dates[i], values.get(i));
		}
	}
	
	public TimeSeries(List<Date> dates, List<Double> values) {
		for (int i=0; i<dates.size(); i++) {
			content.put(dates.get(i), values.get(i));
		}
	}
	
	public TimeSeries(Date[] dates) {
		for (int i=0; i<dates.length; i++) {
			content.put(dates[i], 0.0);
		}
	}
	
	public Set<Entry<Date, Double>> getEntries() {
		return content.entrySet();
	}
	
	public double get(Date key) {
		return content.get(key);
	}
	
	public Date getStartDate() {
		return content.firstKey();
	}
	
	public Date getEndDate() {
		return content.lastKey();
	}
	
	public double getStartValue() {
		return content.firstEntry().getValue();
	}
	
	public double getEndValue() {
		return content.lastEntry().getValue();
	}
	
	public void put(Date key, double value) {
		content.put(key, value);
	}
	
	public boolean hasDate(Date key) {
		return content.containsKey(key);
	}
	
	public int size() {
		return content.size();
	}
	
	public Date[] DatesAsArray() {
		Date[] dates = new Date[content.size()];
		return content.keySet().toArray(dates);
	}
	
	public List<Date> DatesAsList() {
		List<Date> result = new ArrayList<Date>();
		Iterator<Date> iterator = content.keySet().iterator();
		
		while (iterator.hasNext()) {
			result.add((Date)iterator.next());
		}
		
		return result;
	}
	
	public double[] ValuesAsArray() {
		double[] data = new double[content.size()];
		
		int counter = 0;
		for (Iterator<Double> i = content.values().iterator(); i.hasNext(); ) {
			data[counter] = (double)i.next();
			counter++;
		}
		
		return data;
	}
	
	public List<Double> ValuesAsList() {
		List<Double> result = new ArrayList<Double>();
		Iterator<Double> iterator = content.values().iterator();
		
		while (iterator.hasNext()) {
			result.add((double)iterator.next());
		}
		
		return result;
	}
	
	public Date getDayBefore(Date date) {
		return content.lowerKey(date);
	}
	
	public Date getDayAfter(Date date) {
		return content.higherKey(date);
	}
	
	public double getOr0(Date d) {
		Double value = content.get(d);
		return value != null ? value.doubleValue() : 0;
	}
	
	public org.jfree.data.time.TimeSeries convertToChartTimeSeries(String name) {
		org.jfree.data.time.TimeSeries result = new org.jfree.data.time.TimeSeries(name);
		
		for (Map.Entry<Date,Double> entry : content.entrySet()) {
			  result.add(new Day(entry.getKey()), (double)entry.getValue());
		}
		
		return result;
	}
	
	public double getMinValue() {
		if (content.size() == 0)
			return 0;
		
		double result = Double.MAX_VALUE;
		for (Double value : content.values()) {
			result = Math.min(result, value);
		}
		
		return result;
	}
	
	public double getMaxValue() {
		if (content.size() == 0)
			return 0;
		
		double result = Double.MIN_VALUE;
		for (Double value : content.values()) {
			result = Math.max(result, value);
		}
		
		return result;
	}
	
	public TimeSeries extract(TimeRange range) {
		TimeSeries result = new TimeSeries();
		
		for (java.util.Map.Entry<Date, Double> entry : content.entrySet()) {
			if (range.isIn(entry.getKey())) {
				result.put(entry.getKey(), entry.getValue());
			}
		}
		
		return result;
	}
	
	public TimeSeries extract(Date date, int ticksBack) {
		TimeSeries result = new TimeSeries();
		int count = ticksBack;
		Date currentDate = date;
		
		while (count > 0) {
			if (currentDate != null && content.containsKey(currentDate)) {
				double value = content.get(currentDate);
				result.put(currentDate, value);
				currentDate = getDayBefore(currentDate);
			}
			
			count--;
		}
		
		return result;
	}
	
	private static TreeSet<Date> combineDates(TimeSeries a, TimeSeries b) {
		TreeSet<Date> dates = new TreeSet<Date>();
		dates.addAll(a.DatesAsList());
		dates.addAll(b.DatesAsList());
		return dates;
	}
	
	public TimeSeries multiplyWith(TimeSeries other) {
		TimeSeries result = new TimeSeries();
		TreeSet<Date> dates = combineDates(this, other);
		
		for (Date d : dates) {
			result.put(d, this.getOr0(d) * other.getOr0(d));
		}
		
		return result;
	}
	
	public TimeSeries multiplyWith(double factor) {
		TimeSeries result = new TimeSeries();
		
		for (Map.Entry<Date, Double> entry : content.entrySet()) {
			result.put(entry.getKey(), entry.getValue() * factor);
		}
		
		return result;
	}
	
	public TimeSeries divideBy(double quotient) {
		return this.multiplyWith(1 / quotient);
	}
	
	public TimeSeries add(TimeSeries other) {
		TimeSeries result = new TimeSeries();
		TreeSet<Date> dates = combineDates(this, other);
		
		for (Iterator<Date> i=dates.descendingIterator(); i.hasNext();) {
			Date d = i.next();
			result.put(d, this.getOr0(d) + other.getOr0(d));
		}
		
		return result;
	}
	
	public TimeSeries add(double value) {
		TimeSeries result = new TimeSeries();
		
		for (Map.Entry<Date, Double> entry : content.entrySet()) {
			result.put(entry.getKey(), entry.getValue() + value);
		}
		
		return result;
	}
	
	public TimeSeries substract(TimeSeries other) {
		return this.add(other.multiplyWith(-1));
	}
	
	public TimeSeries substract(double value) {
		return this.add(value * -1);
	}
	
	public boolean isValueHigherOrEqual(Date date, double boundary) {
		double value = getOr0(date);
		return (value >= boundary);
	}
	
	public boolean isValueLower(Date date, double boundary) {
		double value = getOr0(date);
		return (value < boundary);
	}
	
	public boolean isValueBetween(Date date, double lowerBoundary, double upperBoundary) {
		double value = getOr0(date);
		return (value > lowerBoundary && value < upperBoundary);
	}
	
	public double sumValues() {
		double value = 0;
		
		for (Map.Entry<Date, Double> entry : content.entrySet()) {
			value += entry.getValue();
		}

		return value;
	}
	
	public TimeSeries clone() {
		TimeSeries result = new TimeSeries();
		
		for (Map.Entry<Date,Double> entry : content.entrySet()) {
			result.put(entry.getKey(), entry.getValue());
		}
		
		return result;	
	}
	
	public TimeSeries filterZeros() {
		TimeSeries result = new TimeSeries();
		
		for (Map.Entry<Date,Double> entry : content.entrySet()) {
			if (entry.getValue() != 0)
				result.put(entry.getKey(), entry.getValue());
		}
		
		return result;
	}
	
	public TimeSeries filterPositiveValues() {
		TimeSeries result = new TimeSeries();
		
		for (Map.Entry<Date,Double> entry : content.entrySet()) {
			if (entry.getValue() > 0)
				result.put(entry.getKey(), entry.getValue());
			else
				result.put(entry.getKey(), 0d);
		}
		
		return result;
	}
	
	public TimeSeries filterNegativeValues() {
		TimeSeries result = new TimeSeries();
		
		for (Map.Entry<Date,Double> entry : content.entrySet()) {
			if (entry.getValue() < 0)
				result.put(entry.getKey(), entry.getValue());
			else
				result.put(entry.getKey(), 0d);
		}
		
		return result;
	}
	
	public TimeSeries filterZeroCrossings() {
		TimeSeries result = new TimeSeries();
		Date[] dates = DatesAsArray();
		
		for (int i=1; i<dates.length; i++) {
			Date today = dates[i];
			Date yesterday = getDayBefore(today);
			double yesterdayValue = getOr0(yesterday);
			double todayValue = getOr0(today);
			
			if (yesterdayValue > 0 && todayValue <= 0) {
				if (Math.abs(yesterdayValue) > Math.abs(todayValue))
					result.put(today, -1d);
				else
					result.put(yesterday, -1d);
			} else if (yesterdayValue <= 0 && todayValue > 0) {
				if (Math.abs(yesterdayValue) > Math.abs(todayValue))
					result.put(today, 1d);
				else
					result.put(yesterday, 1d);
			} else {
				result.put(today, 0d);
			}
		}
		
		if (!result.hasDate(dates[0])) {
			result.put(dates[0], 0d);
		}

		return result;
	}
	
	public List<Date> getUpwardsZeroCrossings() {
		return this.filterZeroCrossings().filterZeros().filterPositiveValues().DatesAsList();
	}
	
	public List<Date> getDownwardsZeroCrossings() {
		return this.filterZeroCrossings().filterZeros().filterNegativeValues().DatesAsList();
	}
	
	public List<TimeRange> getRanges() {
		List<TimeRange> result = new ArrayList<TimeRange>();
		TreeSet<Date> crossings = new TreeSet<Date>(filterZeroCrossings().filterZeros().DatesAsList());

		Date startDate = getStartDate();
		while (crossings.size() > 0) {
			Date endDate = crossings.pollFirst();
			TimeRange range = new TimeRange(startDate, endDate);
			result.add(range);
			startDate = endDate;
		}
		
		TimeRange range = new TimeRange(startDate, getEndDate());
		result.add(range);
		
		return result;
	}
	
	public List<TimeRange> getPositiveRanges() {
		List<TimeRange> result = new ArrayList<TimeRange>();
		
		for (TimeRange range : getRanges()) {
			if (extract(range).sumValues() > 0)
				result.add(range);
		}
		
		return result;
	}
	
	public List<TimeRange> getNegativeRanges() {
		List<TimeRange> result = new ArrayList<TimeRange>();
		
		for (TimeRange range : getRanges()) {
			if (extract(range).sumValues() < 0)
				result.add(range);
		}
		
		return result;
	}
	
	public TimeSeries normValues() {
		TimeSeries result = new TimeSeries();
		
		for (Map.Entry<Date, Double> entry : content.entrySet()) {
			if (entry.getValue() > 0) {
				result.put(entry.getKey(), 1);
			} else if (entry.getValue() < 0) {
				result.put(entry.getKey(), -1);
			} else {
				result.put(entry.getKey(), 0);
			}
		}
		
		return result;
	}
}
